-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local bs = require 'mc.bitstring'
local utils = require 'mc.utils'
local log = require 'mc.logging'
local libmgmt_protocol = require 'libmgmt_protocol'

-- SP382
local CX5 = {
    protocol_dependencies = {ncsi_standard = {endpoint = nil}, ncsi_mellanox = {endpoint = nil}},
    properties = {
        DisableHardwareArbitration = {
            protocol = 'ncsi_standard',
            action = 'on_demand',
            request = {
                channel_id = 0x1F,
                packet_type = 0x01,
                expect_rsp_packet_type = 0x81,
                data = '\x00\x00\x00\x01'
            },
            response = function(data)
                return bs.new([[<<
                    response_code:16/big,
                    reason_code:16/big
                >>]]):unpack(data, true)
            end
        },
        InitializeNCSIChannel = {
            protocol = 'ncsi_standard',
            action = 'on_demand',
            request = {
                -- 运行时需传入channel_id(也就是port id)
                packet_type = 0x00,
                expect_rsp_packet_type = 0x80
            },
            response = function(data)
                return bs.new([[<<
                    response_code:16/big,
                    reason_code:16/big
                >>]]):unpack(data, true)
            end
        },
        VendorID = {
            protocol = 'ncsi_standard',
            action = 'on_schedule',
            period_in_sec = 60,
            request = {
                packet_type = 0x15,
                expect_rsp_packet_type = 0x95,
                channel_id = 0x0 -- 默认从port 0获取
            },
            response = function(data)

                local r = bs.new([[<<
                    response_code:16/big,
                    reason_code:16/big,
                    ncsi_version_major:8,
                    ncsi_version_minor:8,
                    ncsi_version_update:8,
                    ncsi_version_alpha1:8,
                    rsvd0:24,
                    ncsi_version_alpha2:8,
                    firmware_name:12/string,
                    # mellanox firmware version显示为十进制 xx.yy.zzzz,最后一位是16bit大端
                    firmware_version_1:8,
                    firmware_version_2:8,
                    firmware_version_3:16/big,
                    pci_did:16/big,
                    pci_vid:16/big,
                    pci_ssid:16/big,
                    pci_svid:16/big,
                    manufacturer_id:32/big
                >>]]):unpack(data, true)
                local bsd_to_string = function(v)
                    if v & 0xF0 == 0xF0 then
                        return tostring(v)
                    elseif v < 0x10 then
                        return '0' .. tostring(v)
                    end
                    return tostring((v // 16) * 10)
                end
                local ncsi_version = bsd_to_string(r.ncsi_version_major) ..
                                         bsd_to_string(r.ncsi_version_minor)
                if r.ncsi_version_update ~= 0xFF then
                    ncsi_version = ncsi_version .. bsd_to_string(r.ncsi_version_update)
                end
                ncsi_version = ncsi_version .. string.char(r.ncsi_version_alpha1) ..
                                   string.char(r.ncsi_version_alpha2)
                local firmware_version = string.format('%02d.%02d.%04d', r.firmware_version_1,
                    r.firmware_version_2, r.firmware_version_3)
                log:debug('get firmware by ncsi over mctp, value is %s', firmware_version)
                return {
                    FirmwareVersion = firmware_version,
                    VendorID = string.format('0x%04x', r.pci_vid),
                    DeviceID = string.format('0x%04x', r.pci_did),
                    SubsystemVendorID = string.format('0x%04x', r.pci_svid),
                    SubsystemDeviceID = string.format('0x%04x', r.pci_ssid)
                }
            end
        },
        PortMetrics = {
            protocol = 'ncsi_standard',
            action = 'on_schedule',
            period_in_sec = 5,
            request = {
                -- 运行时需传入channel_id(也就是port id)
                packet_type = 0x18,
                expect_rsp_packet_type = 0x98
            },
            response = function(data)
                local r = bs.new([[<<
                    response_code:16/big,
                    reason_code:16/big,
                    counters_cleared_from_last_read_ms:32/big,
                    counters_cleared_from_last_read_ls:32/big,
                    total_bytes_received:64/big,
                    total_bytes_transmitted:64/big,
                    total_unicast_packets_received:64/big,
                    total_multicast_packets_received:64/big,
                    total_broadcast_packets_received:64/big,
                    total_unicast_packets_transmitted:64/big,
                    total_multicast_packets_transmitted:64/big,
                    total_broadcast_packets_transmitted:64/big,
                    fcs_received_errors:32/big,
                    alignment_errors:32/big,
                    false_carrier_detections:32/big,
                    runt_packets_received:32/big,
                    jabber_packets_received:32/big,
                    pause_xon_frames_received:32/big,
                    pause_xoff_frames_received:32/big,
                    pause_xon_frames_transmitted:32/big,
                    pause_xoff_frames_transmitted:32/big,
                    single_collision_transmit_frames:32/big,
                    multiple_collision_transmit_frames:32/big,
                    late_collision_frames:32/big,
                    excessive_collision_frames:32/big,
                    control_frames_received:32/big,
                    byte_64_frames_received:32/big,
                    byte_65_127_frames_received:32/big,
                    byte_128_255_frames_received:32/big,
                    byte_256_511_frames_received:32/big,
                    byte_512_1023_frames_received:32/big,
                    byte_1024_1522_frames_received:32/big,
                    byte_1523_9022_frames_received:32/big,
                    byte_64_frames_transmitted:32/big,
                    byte_65_127_frames_transmitted:32/big,
                    byte_128_255_frames_transmitted:32/big,
                    byte_256_511_frames_transmitted:32/big,
                    byte_512_1023_frames_transmitted:32/big,
                    byte_1024_1522_frames_transmitted:32/big,
                    byte_1523_9022_frames_transmitted:32/big,
                    valid_bytes_received:64/big,
                    error_runt_packets_received:32/big,
                    error_jabber_packets_received:32/big
                >>]]):unpack(data, true)
                return {
                    RXFrames = r.total_bytes_received,
                    TXFrames = r.total_bytes_transmitted,
                    RXUnicastFrames = r.total_unicast_packets_received,
                    RXMulticastFrames = r.total_multicast_packets_received,
                    RXBroadcastFrames = r.total_broadcast_packets_received,
                    TXUnicastFrames = r.total_unicast_packets_transmitted,
                    TXMulticastFrames = r.total_multicast_packets_transmitted,
                    TXBroadcastFrames = r.total_broadcast_packets_transmitted,
                    RXFCSErrors = r.fcs_received_errors,
                    RXUndersizeFrames = r.runt_packets_received,
                    RXOversizeFrames = r.jabber_packets_received,
                    TXSingleCollisions = r.single_collision_transmit_frames,
                    TXMultipleCollisions = r.multiple_collision_transmit_frames,
                    TXLateCollisions = r.late_collision_frames,
                    TXExcessiveCollisions = r.excessive_collision_frames,
                    RXFrameAlignmentErrors = r.alignment_errors
                }
            end
        },
        LinkStatus = {
            protocol = 'ncsi_standard',
            action = 'on_schedule',
            period_in_sec = 5,
            request = {
                -- 运行时需传入channel_id(也就是port id)
                packet_type = 0x0A,
                expect_rsp_packet_type = 0x8A
            },
            response = function(data)
                local r = bs.new([[<<
                    response_code:16/big,
                    reason_code:16/big,

                    # link status 有4个字节，大字节序
                    # link status 第1个字节
                    extended_speed_duplex:8,

                    # link status 第二个字节
                    tx_flow_control:1,
                    rx_flow_control:1,
                    link_partner8:2,
                    serdes_link:1,
                    oem_link_speed:1,
                    rsvd1:2,

                    # link status 第三个字节
                    rsvd2:1,
                    link_partner1:1,
                    link_partner2:1,
                    link_partner3:1,
                    link_partner4:1,
                    link_partner5:1,
                    link_partner6:1,
                    link_partner7:1,

                    # link status 第四个字节
                    link_flag:1,
                    speed_duplex:4,
                    negotiate_flag:1,
                    negotiate_complete:1,
                    parallel_detection:1,

                    # other indication 有4个字节
                    rsvd3:24,
                    host_nc_driver_status_indication:1,
                    rsvd4:7,

                    oem_link_status:32/big
                >>]]):unpack(data, true)

                local speed<const> = {
                    10, 10, 100, 100, 100, 1000, 1000, 10000, 20000, 25000, 40000, 50000, 100000,
                    2500, 0, 1000, 200000, 800000
                }
                local full_duplex_flag<const> = {
                    false, true, false, false, true, false, true, true, true, true, true, true,
                    true, true, false, false
                }
                local link_switch<const> = {[0x0] = 'Disconnected', [0x1] = 'Connected', [0xFF] = 'N/A'}

                local speed_mps = speed[r.speed_duplex] or 0
                if r.speed_duplex == 0xF then
                    speed_mps = speed[r.extended_speed_duplex] or 0
                end
                return {
                    LinkStatus = link_switch[r.link_flag] or 'N/A',
                    SpeedMbps = speed_mps,
                    AutoSpeedNegotiation = r.negotiate_flag == 1,
                    FullDuplex = full_duplex_flag[r.speed_duplex] or false
                }
            end
        },
        DefaultMacAddrNCSI = {
            protocol = 'ncsi_mellanox',
            action = 'on_schedule',
            period_in_sec = 5,
            request = {
                -- 运行时需传入channel_id(也就是port id)
                cmd_mellanox = 0x00,
                mellanox_cmd_id = 0x00,
                parameter = 0x27,
                extra_cmd = 0x00
            },
            response = function(data)

                local r = bs.new([[<<
                    _:8,
                    addr:1/MAC_ADDRESS,
                    rsvd0:16,
                    port_guid:64/big
                >>]], libmgmt_protocol.common_bs_helper):unpack(data, true)
                return r.addr.mac_address
            end
        },
        MacAddrNCSI = {
            protocol = 'ncsi_mellanox',
            action = 'on_schedule',
            period_in_sec = 5,
            request = {
                -- 运行时需传入channel_id(也就是port id)
                cmd_mellanox = 0x00,
                mellanox_cmd_id = 0x00,
                parameter = 0x00,
                extra_cmd = 0x00
            },
            response = function(data)
                local r = bs.new([[<<
                    _:8,
                    addr:1/MAC_ADDRESS,
                    rate_limiter:16/big
                >>]], libmgmt_protocol.common_bs_helper):unpack(data, true)
                return r.addr.mac_address
            end
        },
        ChipTemp = {
            protocol = 'ncsi_mellanox',
            action = 'on_schedule',
            period_in_sec = 2,
            request = {
                channel_id = 0x1F, -- special channel id in application note
                cmd_mellanox = 0x00,
                mellanox_cmd_id = 0x13,
                parameter = 0x02,
                -- 如果需要查询指定port口温度，extra_cmd:0-6 = chip thermal sensor 1~127 board level sensor
                -- extra_cmd:7 = sp=0:on chip sensor
                -- 没有查询指定port口的功能，这里只做记录
                extra_cmd = 0x0
            },
            response = function(data)
                local r = bs.new([[<<
                    _:8,
                    pad_and_mms:8,
                    max_measure_temp:8,
                    max_op_temp:8,
                    current_temp:8/signed
                >>]]):unpack(data, true)
                return r.current_temp
            end
        },
        MaxOpTemp = {
            protocol = 'ncsi_mellanox',
            action = 'on_demand',
            request = {
                channel_id = 0x1F, -- special channel id in application note
                cmd_mellanox = 0x00,
                mellanox_cmd_id = 0x13,
                parameter = 0x02,
                extra_cmd = 0x0
            },
            response = function(data)
                local r = bs.new([[<<
                    _:8,
                    pad_and_mms:8,
                    max_measure_temp:8,
                    max_op_temp:8,
                    current_temp:8/signed
                >>]]):unpack(data, true)
                return r.max_op_temp
            end
        }
    }
}

return {
    smbus = nil,
    mctp = function(endpoint)
        local obj = utils.table_copy(CX5)
        obj.protocol_dependencies.ncsi_mellanox.endpoint = endpoint
        obj.protocol_dependencies.ncsi_standard.endpoint = endpoint
        return obj
    end
}
